package com.nariano.server.services.statistics.impl;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.springframework.util.Assert;

import com.nariano.server.domain.entities.Area;
import com.nariano.server.domain.entities.City;
import com.nariano.server.domain.entities.PopulationType;
import com.nariano.server.domain.entities.User;
import com.nariano.server.domain.utils.PopulationTypeList;
import com.nariano.server.publisher.ClientPublisherListener;
import com.nariano.server.publisher.DefaultEventVisitor;
import com.nariano.server.publisher.Event;
import com.nariano.server.publisher.Visitor;
import com.nariano.server.publisher.events.AreaDiscovered;
import com.nariano.server.publisher.events.CityCreated;
import com.nariano.server.publisher.events.ConflictResolved;
import com.nariano.server.publisher.events.PopulationChanged;
import com.nariano.server.services.cityservice.CityService;
import com.nariano.server.services.statistics.PopulationDistribution;
import com.nariano.server.services.statistics.StatsService;
import com.nariano.server.services.userservice.UserService;
import com.nariano.server.utils.aop.rwsemaphore.ReadTransaction;
import com.nariano.server.utils.aop.rwsemaphore.WriteTransaction;

public class StatsServiceImpl implements ClientPublisherListener, StatsService {
	
	transient Logger logger = Logger.getLogger(StatsServiceImpl.class.getCanonicalName());

	private static final int VICTORY_POINTS = 11;
	private static final int EXPLORER_POINTS = 3;
	
	private volatile long timeStamp = 0;
	private static final long MINUTE_MILLIS = 60 * 1000;
	
    private UserService userService;
	
	private List<City> topList = new LinkedList<City>();

	private CityService cityService;

	private PopulationDistributionContainer populationDistributionContainer;
	
	public StatsServiceImpl(UserService userService, CityService cityService, PopulationDistributionContainer populationDistributionContainer) {
		this.userService = userService;
		this.cityService = cityService;
		this.populationDistributionContainer = populationDistributionContainer;
	}
	
	@Override
	@WriteTransaction
	public void onEvent(Event event) {
		event.visit(new DefaultEventVisitor( ) {
			
			@Override
			public void accept(ConflictResolved a) {
				if (a.getArea().getUsers().size()!=1) {
					logger.severe("Excpected one user got " + a.getArea());
					return;
				}
				addPoints(a.getArea().getUsers().iterator().next(), VICTORY_POINTS);
			}
			
			@Override
			public void accept(AreaDiscovered a) {
				Assert.isTrue(a.getArea().getUsers().size()==1, a.getArea().getUsers().toString());
				addPoints(a.getArea().getUsers().iterator().next(), EXPLORER_POINTS);
			}

			@Override
			public void accept(PopulationChanged a) {
				PopulationType type = a.getType();
				int count = a.getCount();
				Integer owner = a.getUserId();
				addPopulation(owner, type, count);
			}

			@Override
			public void accept(CityCreated a) {
				populationDistributionContainer.addUser(a.getArea().getCity().getOwnerId());
			}
			
		});
	}
	
	protected void addPopulation(Integer user, PopulationType type, int count) {
		populationDistributionContainer.addPopulation(user, type, count);
	}

	protected void addPoints(Integer user, int points) {
		User u = userService.findUserFromId(user);
		u.addPoints(points);
		userService.updateUser(u);
	}
	
	@Override
	@ReadTransaction
	public List<City> getTopList(int max) {
		if (System.currentTimeMillis() - timeStamp > MINUTE_MILLIS) {
			topList = getSortedList();	
			timeStamp = System.currentTimeMillis();
		}
		return topList.subList(0, Math.min(max, topList.size()));
	}

	private synchronized List<City> getSortedList() {
		LinkedList<User> tempList = new LinkedList<User>(userService.getAllUsers());
		Collections.sort(tempList, new Comparator<User> (){

			@Override
			public int compare(User user1, User user2) {
				return user2.getPoints()-user1.getPoints();
			}
			
		});
		
		List<City> tempRes = new LinkedList<City>();
		for (User u:tempList) {
			if (cityService.getCities(u).size()>0) {
				tempRes.add(cityService.getCities(u).get(0));
			}
		}
		
		return tempRes;
	}

	@Override
	public PopulationDistribution getPopulationDistribution(User user) {
		return populationDistributionContainer.get(user.getId());
	}

	
}
